# Copyright 2018-present RebirthDB
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use
# this file except in compliance with the License. You may obtain a copy of the
# License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#
# This file incorporates work covered by the following copyright:
#
#     Copyright 2010-present, The Linux Foundation, portions copyright Google and
#     others and used with permission or subject to their respective license
#     agreements.
#
#     Licensed under the Apache License, Version 2.0 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.

'''
Take a .proto file as input and output the a definitions file for a
supported language: javascript, python, ruby

Usually the input file should be ../src/rdb_protocol/ql2.proto
'''

import os
import re
import sys

languageDefs = {
    "python": {
        "initialIndentLevel": 0,
        "header": "# DO NOT EDIT\n# Autogenerated by %s\n" %
        os.path.basename(__file__),
        "separator": "",
        "open": "\n%(tabs)sclass %(name)s:",
        "value": "\n%(tabs)s%(name)s = %(value)s",
        "empty": "pass",
        "close": None,
        "closeAlwaysNewLine": False,
        "footer": "\n"
    },
    "ruby": {
        "initialIndentLevel": 1,
        "header": "# DO NOT EDIT\n# Autogenerated by %s\n\nmodule RethinkDB"
        % os.path.basename(__file__),
        "separator": "",
        "open": "\n%(tabs)smodule %(name)s",
        "value": "\n%(tabs)s%(name)s = %(value)s",
        "empty": None,
        "close": "end",
        "closeAlwaysNewLine": True,
        "footer": "\nend\n"
    },
    "javascript": {
        "initialIndentLevel": 1,
        "header":
        "// DO NOT EDIT\n// Autogenerated by %s\n\nmodule.exports = {"
        % os.path.basename(__file__),
        "separator": ",",
        "open": "\n%(tabs)s%(name)s: {",
        "value": "\n%(tabs)s%(name)s: %(value)s",
        "empty": None,
        "close": "}",
        "closeAlwaysNewLine": False,
        "footer": "\n}\n"
    }
}


def convertFile(inputFile, outputFile, language):
    assert(inputFile is not None and hasattr(inputFile, 'read'))
    assert(outputFile is not None and hasattr(outputFile, 'write'))
    assert(language in languageDefs)

    messageRegex = re.compile('\s*(message|enum) (?P<name>\w+) \{')
    valueRegex = re.compile('\s*(?P<name>\w+)\s*=\s*(?P<value>\w+)')
    endRegex = re.compile('\s*\}')

    indentLevel = languageDefs[language]["initialIndentLevel"]
    lastIndentLevel = languageDefs[language]["initialIndentLevel"] - 1

    # -- write headers

    outputFile.write(languageDefs[language]["header"])

    # -- convert the body

    levelHasContent = False

    for line in inputFile:
        # - open
        match = messageRegex.match(line)
        if match is not None:
            if indentLevel == lastIndentLevel:
                outputFile.write(languageDefs[language]["separator"])
            if levelHasContent:
                outputFile.write("\n" + "\t" * indentLevel)
            outputFile.write(languageDefs[language]["open"] % {
                'tabs': "\t" * indentLevel,
                'name': match.group('name')
            })
            lastIndentLevel = indentLevel
            indentLevel += 1
            levelHasContent = False
            continue

        # - value
        match = valueRegex.match(line)
        if match is not None:
            if indentLevel == lastIndentLevel:
                outputFile.write(languageDefs[language]["separator"])
            value = match.group('value')
            if value.startswith('0x'):
                value = int(value, 0)
            outputFile.write(languageDefs[language]["value"] % {
                'tabs': "\t" * indentLevel,
                'name': match.group('name'),
                'value': value,
            })
            lastIndentLevel = indentLevel
            levelHasContent = True
            continue

        # - close
        match = endRegex.match(line)
        if match is not None:
            if not levelHasContent and \
               languageDefs[language]["empty"] is not None:
                outputFile.write(
                    "\n" + "\t" * indentLevel +
                    languageDefs[language]["empty"]
                )
                lastIndentLevel = indentLevel
            if languageDefs[language]["close"] is not None:
                if indentLevel == lastIndentLevel or \
                   languageDefs[language]["closeAlwaysNewLine"] is True:
                    outputFile.write("\n" + "\t" * (indentLevel - 1))
                outputFile.write(languageDefs[language]["close"])
            indentLevel -= 1
            lastIndentLevel = indentLevel
            levelHasContent = True

    # -- write footer
    outputFile.write(languageDefs[language]["footer"])

if __name__ == '__main__':
    import optparse

    inputFile = sys.stdin
    outputFile = sys.stdout

    # -- parse input

    parser = optparse.OptionParser()
    parser.add_option(
        "-l", "--language",
        dest="language",
        help="write output for language",
        metavar="LANG",
        choices=list(languageDefs.keys()),
        default=None,
    )
    parser.add_option(
        "-i", "--input-file",
        dest="inputFile",
        help="read from FILE (default STDIN)",
        metavar="FILE",
        default=None,
    )
    parser.add_option(
        "-o", "--output-file",
        dest="outputFile",
        help="write to FILE (default STDOUT)",
        metavar="FILE",
        default=None,
    )

    (options, args) = parser.parse_args()

    if options.language is None:
        parser.error("A language option is required")

    if options.inputFile is not None:
        try:
            inputFile = open(options.inputFile, 'r')
        except Exception as e:
            parser.error("Unable to open the given input file <<%s>>"
                         ", got error: %s" % (inputFile, str(e)))

    if options.outputFile is not None:
        try:
            outputFile = open(options.outputFile, 'w')
        except Exception as e:
            parser.error("Unable to open the given output file <<%s>>,"
                         " got error: %s" % (outputFile, str(e)))

    convertFile(inputFile, outputFile, options.language)
